﻿//MarketEmulator.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Testing
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;
	using StockSharp.Logging;

	/// <summary>
	/// Эмулятор биржи.
	/// </summary>
	public class MarketEmulator : Disposable, IMarketEmulator
	{
		private sealed class PriorityMessageQueue : ICollection<Message>
		{
			private readonly object _syncRoot = new object();

			private readonly PriorityQueue<DateTime, Message> _queue = new PriorityQueue<DateTime, Message>();

			public void Enqueue(Message msg)
			{
				lock (_syncRoot)
					_queue.Enqueue(msg.Time, msg);
			}

			public Message TryPeek()
			{
				lock (_syncRoot)
					return _queue.Count > 0 ? _queue.PeekValue() : null;
			}

			public Message Peek()
			{
				lock (_syncRoot)
					return _queue.PeekValue();
			}

			public Message Dequeue()
			{
				lock (_syncRoot)
					return _queue.Dequeue().Value;
			}

			public void Add(Message item)
			{
				Enqueue(item);
			}

			public void Clear()
			{
				lock (_syncRoot)
					_queue.Clear();
			}

			public bool Contains(Message item)
			{
				lock (_syncRoot)
					return _queue.Contains(new KeyValuePair<DateTime, Message>(item.Time, item));
			}

			public void CopyTo(Message[] array, int arrayIndex)
			{
				throw new NotSupportedException();
			}

			public int Count
			{
				get { return _queue.Count; }
			}

			public bool IsReadOnly
			{
				get { return false; }
			}

			public bool Remove(Message item)
			{
				lock (_syncRoot)
					return _queue.Remove(new KeyValuePair<DateTime, Message>(item.Time, item));
			}

			public IEnumerator<Message> GetEnumerator()
			{
				foreach (var kv in _queue)
					yield return kv.Value;
			}

			IEnumerator IEnumerable.GetEnumerator()
			{
				return GetEnumerator();
			}
		}

		private readonly ITrader _trader;

		private readonly SynchronizedList<OrderFail> _orderRegisterFails = new SynchronizedList<OrderFail>(10);
		private readonly SynchronizedList<OrderFail> _orderCancelFails = new SynchronizedList<OrderFail>(10);
		private readonly SynchronizedList<Order> _orders = new SynchronizedList<Order>(1000);
		private readonly SynchronizedList<MyTrade> _myTrades = new SynchronizedList<MyTrade>(10000);

		// входящие транзакции регистер, кэнсел, ререгистер. 
		private readonly PriorityMessageQueue _incomingQueue = new PriorityMessageQueue();
		private readonly PriorityMessageQueue _outgoingQueue = new PriorityMessageQueue();

		private readonly Dictionary<long,LinkedListNode<Order>> _byId = new Dictionary<long, LinkedListNode<Order>>();
		private readonly Dictionary<long,List<Trade>>  _matches = new Dictionary<long, List<Trade>>();

		private readonly IdGenerator _orderIdGenerator = new IdGenerator();
		private readonly IdGenerator _tradeIdGenerator = new IdGenerator();

		private sealed class Auction
		{
			private sealed class BackComparer : Comparer<decimal>
			{
				public override int Compare(decimal a, decimal b)
				{
					return -a.CompareTo(b);
				}
			}

			private sealed class OrderLevel : LinkedList<Order>
			{
				private bool _needLocal;

				public void NeedLocal()
				{
					_needLocal = true;
				}

				public Quote Quote { get; private set; }

				public Quote GetQuote()
				{
					if (_needLocal)
					{
						Quote = Quote.Clone();
						_needLocal = false;
					}

					return Quote;
				}

				public OrderLevel(Security security, decimal price, decimal volume, OrderDirections dir)
				{
					Quote = new Quote(security, price, volume, dir);
					_needLocal = false;
				}
			}

			private readonly SortedList<decimal, OrderLevel> _bids = new SortedList<decimal, OrderLevel>(new BackComparer());
			private readonly SortedList<decimal, OrderLevel> _asks = new SortedList<decimal, OrderLevel>();

			private readonly MarketEmulator _parent;

			private object SyncRoot { get; set; }

			public Security Security { get; private set; }

			public Auction(Security s, MarketEmulator parent)
			{
				if (parent == null)
					throw new ArgumentNullException("parent");

				_parent = parent;
				Security = s;
				Settings = new SecurityEmulationSettings(_parent.Settings);
				GetDepth = a => a.Depth;
				
				//Depth = new MarketDepth(Security);
				Trades = new List<Trade>();
				SyncRoot = new object();
				MaxDepth = 100;
			}

			private static bool IsCrossRef(Order a, Order b)
			{
				return a.Portfolio != null && b.Portfolio == a.Portfolio;
			}

			public MarketDepth PrevDepth { get; set; }
			/// <summary>
			/// Текущий стакан.
			/// </summary>
			public MarketDepth Depth { get; set; }

			/// <summary>
			/// Сделки, прошедшие после последнего стакана.
			/// </summary>
			public List<Trade> Trades { get; private set; }

			public Trade LastTrade { get; set; }

			/// <summary>
			/// Функция которая синхронизирует стакан с прошедшими после него сделками.
			/// </summary>
			public Func<Auction, MarketDepth> GetDepth { get; set; }

			private Order[] _cachedOrders;

			public IEnumerable<Order> CachedOrders
			{
				get
				{
					lock (SyncRoot)
					{
						return _cachedOrders ?? (_cachedOrders = _bids.Values.SelectMany(ol => ol).Concat(_asks.Values.SelectMany(ol => ol)).ToArray());
					}
				}
			}

			public SecurityEmulationSettings Settings { get; private set; }

			/// <summary>
			/// Использовать для эмуляции только ордерлог. Входящие стаканы и сделки игнорируются.
			/// Создаются эмуляционные стаканы (если Settings.UseMarketDepth==true).
			/// </summary>
			public bool EmulateByOrderLog
			{
				get { return Settings.UseOrderLog; }
			}

			public Order New(Order order)
			{
				if (_parent._byId.ContainsKey(order.Id))
					throw new ArgumentException("Заявка {0} уже была зарегистрирована.".Put(order.Id), "order");

				var newState = CloneOrderState(order);

				newState.State = OrderStates.Active;
				newState.LastChangeTime = _parent.EmulatorToExchange(order.Security, _parent.CurrentTime);
				newState.Time = newState.LastChangeTime;

				if (newState.Id == 0)
					newState.Id = _parent._orderIdGenerator.Next;

				newState.Balance = order.Volume;

				_parent.AuctionOnNewOrder(newState);

				return newState;
			}

			public static Order CloneOrderState(Order order)
			{
				return new Order
				{
					Id = order.Id,
					TransactionId = order.TransactionId,
					Price = order.Price,
					Type = order.Type,
					Security = order.Security,
					Portfolio = order.Portfolio,
					Volume = order.Volume,
					Balance = order.Balance,
					Direction = order.Direction,
					State = order.State,
					Status = order.Status,
					ExecutionCondition = order.ExecutionCondition,
					Time = order.Time,
					LastChangeTime = order.LastChangeTime
				};
			}

			public LinkedListNode<Order> FindCrossRefs(Order newOrder)
			{
				if (newOrder == null)
					throw new ArgumentNullException("newOrder");

				var theirQueue = newOrder.Direction == OrderDirections.Sell ? _bids : _asks;
				var sign = newOrder.Direction == OrderDirections.Sell ? -1 : 1;
				var price = newOrder.Price;

				const int idx = 0;

				while (idx < theirQueue.Count && decimal.Compare(theirQueue.Keys[idx], price) != sign)
				{
					var level = theirQueue.Values[idx];

					for (var node = level.First; node != null;)
					{
						var next = node.Next;

						if (IsCrossRef(node.Value, newOrder))
							return node;

						node = next;
					}
				}

				return null;
			}

			public void Put(Order newOrder)
			{
				if (newOrder == null)
					throw new ArgumentNullException("newOrder");

				if (newOrder.Balance <= 0)
					throw new ArgumentException("У новой заявки должен быть положительный баланс.", "newOrder");

				var theirQueue = newOrder.Direction == OrderDirections.Sell ? _bids : _asks;
				var sign = newOrder.Direction == OrderDirections.Sell ? -1 : 1;
				var price = newOrder.Price;
				decimal levelPrice;
				var idx = 0;

				while (idx < theirQueue.Count && decimal.Compare(levelPrice = theirQueue.Keys[idx], price) != sign)
				{
					var level = theirQueue.Values[idx];
					for (var node = level.First; node != null; )
					{
						var next = node.Next;

						if (node.Value.Balance <= 0 || newOrder.Balance <= 0)
						{
							throw new InvalidOperationException("Сводимые заявки {0} и {1} имеют нулевой баланс.".Put(node.Value.Id, newOrder.Id));
						}

						if (IsCrossRef(node.Value, newOrder))
						{
							newOrder.State = OrderStates.Done;

							_parent.AuctionOnOrderChanged(newOrder);

							_parent.ProcessError(newOrder, OrderTraceActions.Registering, 
								new InvalidOperationException("Кросс-сделка для заявок {0} и {1}.".Put(newOrder.TransactionId, node.Value.TransactionId)));

						}

						var tradeVolume = node.Value.Balance.Min(newOrder.Balance);

						if (Fill(node, newOrder, levelPrice, tradeVolume))
							idx--;

						newOrder.Balance -= tradeVolume;

						if (newOrder.Balance == 0)
							newOrder.State = OrderStates.Done;

						newOrder.LastChangeTime = _parent.EmulatorToExchange(newOrder.Security, _parent.CurrentTime);

						_parent.AuctionOnOrderChanged(newOrder);

						if (newOrder.State == OrderStates.Done)
						{
							// новый ордер не попадает стакан.
							return;	
						}

						node = next;
					}

					idx++;
				}

				// встречные и fill-or-kill заявки
				if (newOrder.ExecutionCondition == OrderExecutionConditions.CancelBalance || newOrder.ExecutionCondition == OrderExecutionConditions.MatchOrCancel)
				{
					newOrder.State = OrderStates.Done;
					_parent.AuctionOnOrderChanged(newOrder);
					DepthChanged();
				}
				else
				{
					PutInQueue(newOrder);
				}
			}

			public LinkedListNode<Order> PutInQueue(Order newOrder)
			{
				var ourQueue = newOrder.Direction == OrderDirections.Sell ? _asks : _bids;

				OrderLevel ourlevel;
				LinkedListNode<Order> newNode;

				var dir = newOrder.Direction;
				var sec = newOrder.Security;
				var price = newOrder.Price;

				if (!ourQueue.TryGetValue(price, out ourlevel))
				{
					ourQueue.Add(price, ourlevel = new OrderLevel(sec, price, newOrder.Balance, dir));
					newNode = ourlevel.AddLast(newOrder);
				}
				else
				{
					newNode = ourlevel.AddLast(newOrder);
					ourlevel.GetQuote().Volume += newOrder.Balance;
				}

				DepthChanged();

				_cachedOrders = null;
				_parent.AddNode(newNode);

				_parent.AddDebugLog("PUT {0} REST {1}", newNode.Value, ourlevel.Quote.Volume);

				if (newNode.Value.Balance <= 0 || newNode.Value.Id == 0 || newNode.Value.LastChangeTime == default(DateTime))
					throw new InvalidOperationException("Ошибка при постановке заявки.");

				return newNode;
			}

			public bool Fill(LinkedListNode<Order> node, Order newOrder, decimal levelPrice, decimal tradeVolume)
			{
				var levelRemoved = false;

				// ищем сделку в ордерлоге или сгенерировать новую.
				// newOrder=null означает 
				var trade = _parent.GetMatch(newOrder ?? node.Value, levelPrice, tradeVolume);

				var time = _parent.EmulatorToExchange(trade.Security, _parent.CurrentTime);

				trade.Time = time;

				_parent.AuctionOnNewTrade(node.Value, newOrder, trade);

				node.Value.Balance -= tradeVolume;

				var level = node.List as OrderLevel;
				if (null == level)
					throw new ArgumentException("node");

				level.GetQuote().Volume -= tradeVolume;
				node.Value.LastChangeTime = time;

				if (node.Value.Balance == 0)
				{
					node.Value.State = OrderStates.Done;
					levelRemoved = Remove(node);
				}

				_parent.AuctionOnOrderChanged(node.Value);

				DepthChanged();

				return levelRemoved;
			}

			private void DepthChanged()
			{
				if (!EmulateByOrderLog)
					return;

				GetDepth = di =>
				{
					GetDepth = a => a.Depth;
					return di.SyncDepthToOrders();
				};

				_parent.AuctionDepthChanged(this);
			}

			private int MaxDepth { get; set; }

			private MarketDepth SyncDepthToOrders()
			{
				Depth = new MarketDepth(Security);
				
				var bidsV = _bids.Values.ToArray();
				var count = Math.Min(bidsV.Length, MaxDepth);
				var bids = new Quote[count];
				for (var i = 0; i < count; i++)
				{
					bids[i] = bidsV[i].Quote;
					bidsV[i].NeedLocal();
				}
				
				var asksV = _asks.Values.ToArray();
				count = Math.Min(asksV.Length, MaxDepth);
				var asks = new Quote[count];
				for (var i = 0; i < count; i++)
				{
					asks[i] = asksV[i].Quote;
					asksV[i].NeedLocal();
				}

				Depth.Update(bids, asks, _parent.CurrentTime);
				//_parent.AddDebugLog("SYNC MD {0}", Depth.BestPair);
				return Depth;
			}

			public void Cancel(LinkedListNode<Order> node)
			{
				Remove(node);
				node.Value.State = OrderStates.Done;
				node.Value.LastChangeTime = _parent.EmulatorToExchange(node.Value.Security, _parent.CurrentTime);
				_parent.AuctionOnOrderChanged(node.Value);
			}

			public bool Remove(LinkedListNode<Order> node)
			{
				var order = node.Value;
				var levelRemoved = false;

				OrderLevel level;
				var queue = (order.Direction == OrderDirections.Buy ? _bids : _asks);

				queue.TryGetValue(order.Price, out level);

				if (node.List == null || node.List != level)
					throw new InvalidOperationException("Ошибка при удалении заявки {0}.".Put(order.Id));

				//var level = node.List as OrderLevel;
				level.Remove(node);
				level.Quote.Volume -= order.Balance;

				_parent.AddDebugLog("REMOVE {0} REST {1}", node.Value, level.Quote.Volume);

				if (level.Count == 0 && level.Quote.Volume == 0)
					levelRemoved = queue.Remove(level.Quote.Price);
				else if (!(level.Count > 0 && level.Quote.Volume > 0))
					throw new InvalidOperationException("Ошибка с подсчетом объема заявки {0}.".Put(order.Id));
				
				_cachedOrders = null;
				_parent.RemoveNode(node);

				return levelRemoved;
			}
		}

		private readonly Dictionary<Security, Auction> _bySecurity = new Dictionary<Security, Auction>();

		private LinkedListNode<Order> GetNode(long id)
		{
			return _byId.TryGetValue(id);
		}

		private void AddNode(LinkedListNode<Order> node)
		{
			_byId.Add(node.Value.Id, node);
		}

		private void RemoveNode(LinkedListNode<Order> node)
		{
			var res = _byId.Remove(node.Value.Id);

			if (!res)
				throw new InvalidOperationException("Not found/remove "+node.Value.Id);
		}

		private Auction GetSecurityAuction(Security security)
		{
			return _bySecurity.SafeAdd(security, sec => new Auction(security, this));
		}

		private Trade GetMatch(Order order, decimal price, decimal volume)
		{
			Trade trade = null;

			if (Settings.UseOrderLogTrades)
			{
				var list = _matches.TryGetValue(order.Id);

				if (list != null)
				{
					foreach (var t in list)
					{
						if (t.Volume != volume || t.Price != price)
							continue;

						list.Remove(t);
						trade = t;
						break;
					}
				}
			}

			if (null == trade)
			{
				trade = new Trade
				{
					Security = order.Security,
					Id = _tradeIdGenerator.Next,
					OrderDirection = order.Direction,
					Price = price,
					Volume = volume,
				};
			}

			trade.Time = EmulatorToExchange(order.Security, CurrentTime);
			return trade;
		}

		private DateTime _firstOrderTime;
		private DateTime _lastOrderTime;


		/// <summary>
		/// Инициализировать <see cref="MarketEmulator"/>.
		/// </summary>
		/// <param name="trader">Шлюз к торговой системе.</param>
		public MarketEmulator(ITrader trader)
		{
			if (trader == null)
				throw new ArgumentNullException("trader");

			_trader = trader;
			OrdersKeepTime = TimeSpan.FromDays(1);
			Settings = new MarketEmulatorSettings();
		}

		/// <summary>
		/// Получить настройки для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Настройки для инструмента.</returns>
		public SecurityEmulationSettings GetSettings(Security security)
		{
			return GetSecurityAuction(security).Settings;
		}

		internal void RaiseSecurityDataInfo(IEnumerable<SecurityDataInfo> sdi)
		{
			foreach (var info in sdi)
			{
				GetSettings(info.Security).UseOrderLog = info.Types.Contains(MarketDataTypes.OrderLog);
			}
		}

		internal void RaiseNewTrades(IEnumerable<Trade> trades)
		{
			NewTrades.SafeInvoke(trades);
		}

		internal void RaiseDepthChanged(IEnumerable<MarketDepth> depths )
		{
			MarketDepthsChanged.SafeInvoke(depths);
		}

		internal void RaiseOrderLog(IEnumerable<OrderLogItem> events)
		{
			NewOrderLogItems.SafeInvoke(events);
		}

		internal void RaiseNewMyTrades(IEnumerable<MyTrade> trades)
		{
			_myTrades.AddRange(trades);
			RecycleOrders();

			NewMyTrades.SafeInvoke(trades);
		}

		internal void RaiseNewOrder(Order order)
		{
			_orders.Add(order);
			RecycleOrders();

			NewOrder.SafeInvoke(order);
		}

		internal void RaiseOrderChanged(Order state)
		{
			OrderChanged.SafeInvoke(state);
		}

		internal void RaiseOrderFailed(OrderFail fail)
		{
			if (fail.Order.Id != 0)
				_orderCancelFails.Add(fail);
			else
				_orderRegisterFails.Add(fail);

			OrderFailed.SafeInvoke(fail);
		}

		/// <summary>
		/// Максимальное время хранения заявок. По умолчанию 1 день.
		/// </summary>
		public TimeSpan OrdersKeepTime { get; set; }

		private void RecycleOrders()
		{
			if (OrdersKeepTime == TimeSpan.Zero)
				return;

			var diff = _lastOrderTime - _firstOrderTime;

			if (diff <= TimeSpan.FromTicks(OrdersKeepTime.Ticks*2))
				return;

			_firstOrderTime = _lastOrderTime - OrdersKeepTime;

			_orders.SyncDo(d => d.RemoveWhere(o => o.State == OrderStates.Done && o.Time < _firstOrderTime));
		}

		/// <summary>
		/// Настройки эмулятора биржи.
		/// </summary>
		public MarketEmulatorSettings Settings { get; private set; }

		/// <summary>
		/// Получить все ошибки регистрации заявок.
		/// </summary>
		public IEnumerable<OrderFail> OrderRegisterFails
		{
			get { return _orderRegisterFails.SyncGet(c => c.ToArray()); }
		}

		/// <summary>
		/// Получить все ошибки снятия заявок.
		/// </summary>
		public IEnumerable<OrderFail> OrderCancelFails
		{
			get { return _orderCancelFails.SyncGet(c => c.ToArray()); }
		}

		/// <summary>
		/// Получить все заявки.
		/// </summary>
		public IEnumerable<Order> Orders
		{
			get { return _orders.SyncGet(c => c.ToArray()); }
		}

		/// <summary>
		/// Получить все собственные сделки.
		/// </summary>
		public IEnumerable<MyTrade> MyTrades
		{
			get { return _myTrades.SyncGet(c => c.ToArray()); }
		}

		/// <summary>
		/// Изменилось время в эмуляторе (<see cref="CurrentTime"/>).
		/// </summary>
		public event Action MarketTimeChanged;

		/// <summary>
		/// Событие появления новой заявки.
		/// </summary>
		public event Action<Order> NewOrder;

		/// <summary>
		/// Событие изменения состояния заявки (снята, удовлетворена).
		/// </summary>
		public event Action<Order> OrderChanged;

		/// <summary>
		/// Событие об ошибке регистрации или отмены заявки.
		/// </summary>
		public event Action<OrderFail> OrderFailed;

		/// <summary>
		/// Событие, сигнализирующее об ошибке при получении или обработке новых данных.
		/// </summary>
		public event Action<Exception> ProcessDataError;

		/// <summary>
		/// Событие появления собственных новых сделок.
		/// </summary>
		public event Action<IEnumerable<MyTrade>> NewMyTrades;

		/// <summary>
		/// Событие появления новых записей в логе заявок.
		/// </summary>
		public event Action<IEnumerable<OrderLogItem>> NewOrderLogItems;

		/// <summary>
		/// Событие появления всех новых сделок.
		/// </summary>
		public event Action<IEnumerable<Trade>> NewTrades;

		/// <summary>
		/// Событие изменения стаканов с котировками.
		/// </summary>
		public event Action<IEnumerable<MarketDepth>> MarketDepthsChanged;

		/// <summary>
		/// Оповещение об отсылке входного сообщения в очередь.
		/// </summary>
		public event Action<Message> MessageSent;

		/// <summary>
		/// Оповещение о приходе сообщения. Это происходит в момент чтения из исходящей очереди.
		/// </summary>
		public event Action<Message> MessageReceived;

		/// <summary>
		/// Зарегистрировать заявку в эмуляторе.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		public virtual void RegisterOrder(Order order)
		{
			order.ChechOrderState();

			if (order.Id != 0)
				throw new ArgumentException("Заявка уже имеет номер '{0}'. Возможно, она уже была зарегистрирована.".Put(order.Id), "order");

			if (order.State != OrderStates.None)
				throw new ArgumentException("Заявка уже имеет состояние '{0}'. Возможно, она уже была зарегистрирована.".Put(order.State), "order");

			if (order.Type != OrderTypes.Conditional && order.Type != OrderTypes.Limit && order.Type != OrderTypes.Market)
				throw new ArgumentException("Не поддерживаемый тип заявки {0}.".Put(order.Type.GetName()), "order");

			order.Balance = order.Volume;
			order.Trader = _trader;

			EnqueueOrderMessage(order, OrderTraceActions.Registering, null);
		}

		/// <summary>
		/// Перерегистрировать заявку в эмуляторе.
		/// </summary>
		/// <param name="order">Старая заявка.</param>
		/// <param name="newOrder">Новая заявка.</param>
		public virtual void ReRegisterOrder(Order order, Order newOrder)
		{
			order.ChechOrderState();

			if (newOrder.Id != 0)
				throw new ArgumentException("Новая Заявка уже имеет номер '{0}'. Возможно, она уже была зарегистрирована.".Put(newOrder.Id), "newOrder");

			//if (order.State != OrderStates.Active)
				//throw new ArgumentException("Старая Заявка уже имеет состояние '{0}'. Возможно, она уже была отменена или исполнена.".Put(order.State), "order");

			if (order.Type != OrderTypes.Limit)
				throw new ArgumentException("Не поддерживаемый тип заявки {0}.".Put(order.Type.GetName()), "order");

			newOrder.Balance = order.Volume;
			newOrder.Trader = _trader;

			EnqueueOrderMessage(order, OrderTraceActions.ReRegistering, newOrder);			
		}

		//private bool GetFail()
		//{
		//    return RandomGen.GetDouble() < (Settings.Failing / 100.0);
		//}

		// время прихода клиенту последнего сообщения по каждому потоку. Последующие сообщения приходят строго позже, чтобы не было перепутывания внутри одного потока.
		// TODO: справедливо для ФОРТС ПЛАЗА, но не других бирж.
		private readonly Dictionary<MarketDataTypes, DateTime> _lastMessageTime = new Dictionary<MarketDataTypes, DateTime>();

		private TimeSpan GetLatency(Message msg, bool isOutput)
		{
			if (msg is OrderTransactionMessage || msg is OrderStateMessage)
				return Settings.Latency;

			if (!isOutput)
				return TimeSpan.Zero;

			if (msg is DepthMessage || msg is OrderLogMessage || msg is TradeMessage || msg is MyTradeMessage)
				return Settings.Latency;

			return TimeSpan.Zero;
		}

		private void GenerateLatency(Message msg, bool isOutput)
		{
			var latency = GetLatency(msg, isOutput);

			var lastTime = _lastMessageTime.SafeAdd(msg.DataType, type => msg.Time);

			msg.Time = CurrentTime + latency;

			if (msg.Time <= lastTime)
				msg.Time = lastTime.AddTicks(1);

			_lastMessageTime[msg.DataType] = msg.Time;
		}

		private void EnqueueOrderMessage(Order order, OrderTraceActions action, Order newOrder)
		{
			var tr = new OrderTransactionMessage(CurrentTime, order, action, newOrder);
			GenerateLatency(tr, false);
			EnqueueMessage(tr);
		}

		/// <summary>
		/// Дбавить входное сообщение в очередь.
		/// </summary>
		/// <param name="message">Входное сообщение.</param>
		public void EnqueueMessage(Message message)
		{
			AddDebugLog("IN", () => message);

			var head = _incomingQueue.TryPeek();

			if (head != null)
				CheckMessage(message);
			
			MessageSent.SafeInvoke(message);
			_incomingQueue.Enqueue(message);
		}

		internal void RaiseMarketTimeChanged()
		{
			MarketTimeChanged.SafeInvoke();
		}

		private void CheckMessage(Message message)
		{
			if (message == null)
				throw new ArgumentNullException("message");

			if (message.Time < CurrentTime)
				throw new ArgumentException("Новое событие {0} имеет дату более раннюю, чем текущее время эмулятора {1}.".Put(message, CurrentTime), "message");
		}

		/// <summary>
		/// Добавить сообщение в выходную очередь.
		/// </summary>
		/// <param name="message"></param>
		internal void EnqueueOutputMessage(Message message)
		{
			GenerateLatency(message, true);
			AddDebugLog("OUT", () => message);

			CheckMessage(message);

			_outgoingQueue.Enqueue(message);
		}

		private void AddDebugLog(string msg, params object[] args)
		{
			_trader.AddDebugLog(msg, args);
		}

		private void AddDebugLog(string where, Func<Message> getMsg)
		{
			var input = _incomingQueue.Count;
			var output = _outgoingQueue.Count;

			_trader.AddLog(new LogMessage(_trader, _trader.CurrentTime, LogLevels.Debug, () =>
			{
				var msg = getMsg();
				return "{0}:{1}@{2:HH:mm:ss.fff}[{3},{4}]".Put(@where, msg, msg.Time, input, output);
			}));
		}

		/// <summary>
		/// Отменить заявку в эмуляторе.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменить.</param>
		public virtual void CancelOrder(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			EnqueueOrderMessage(order, OrderTraceActions.Canceling, null);
		}

		/// <summary>
		/// Обновить эмулятор новыми данными о стаканах.
		/// </summary>
		/// <param name="marketDepth">Новый стакан.</param>
		/// <param name="delta">Приращение от старого стакана к новому. Если значение null, прарещение будет вычислено автоматически.</param>
		public virtual void UpdateQuotes(MarketDepth marketDepth, MarketDepth delta)
		{
			if (marketDepth == null)
				throw new ArgumentNullException("marketDepth");

			Emulate(new DepthMessage(marketDepth.LastChangeTime, marketDepth, delta));
		}

		internal void OnDepthChanged(DepthMessage node)
		{
			var depth = node.Events[0];

			if (depth == null)
				throw new ArgumentException("node");

			var si = GetSecurityAuction(depth.Security);

			if (!si.EmulateByOrderLog)
			{
				si.Trades.Clear();
				si.GetDepth = di => di.Depth;

				MarketDepth delta = null;
				if (node.Deltas != null && node.Deltas.Count > 0)
					delta = node.Deltas[0];

				if (delta == null)
				{
					delta = si.PrevDepth != null ? si.PrevDepth.GetDiff(depth) : depth;
				}

				if (!depth.IsFullEmpty())
				{
					si.PrevDepth = si.Depth;
					si.Depth = depth;
				}
				else
				{
					si.PrevDepth = si.Depth = null;
				}

				if (!delta.IsFullEmpty())
				{
					// именно приращением матчим ордера стратегии в стакане. Таким образом огромный ордер не 
					// будет отматчен наехавшим на него стаканом за 100 обновлений
					foreach (var order in si.CachedOrders)
					{
						if (order.Direction == OrderDirections.Buy && delta.Asks.Length == 0)
							continue;
						if (order.Direction == OrderDirections.Sell && delta.Bids.Length == 0)
							continue;

						EvaluateByDepth(order, delta, false);
					}
				}
				// в отлчие от сделок и ОЛ стаканы не буферизируются.
				EnqueueOutputMessage(node);
			}
			else
			{
				ProcessDataError(new InvalidOperationException("В режиме ордерлога входящие стаканы запрещены"));
			}
		}

		private static MarketDepth DepthClone(MarketDepth depth)
		{
			var clone = new MarketDepth(depth.Security);
			clone.Update(depth.Bids, depth.Asks, depth.LastChangeTime);
			return clone;
		}

		/// <summary>
		/// Обновить эмулятор новыми данными о сделках.
		/// </summary>
		/// <param name="newTrades">Новые сделки.</param>
		public virtual void UpdateTrades(IEnumerable<Trade> newTrades)
		{
			if (newTrades == null)
				throw new ArgumentNullException("newTrades");

			Emulate(new TradeMessage(newTrades.First().Time, newTrades));
		}

		/// <summary>
		/// Обновить эмулятор новыми данными об ордерлоге.
		/// </summary>
		/// <param name="newItems">Новые сделки.</param>
		public virtual void UpdateOrderLog(IEnumerable<OrderLogItem> newItems)
		{
			var ol = newItems.First();
			Emulate(new OrderLogMessage(ol.Order.Time, ol));
		}

		internal void OnNewTrades(TradeMessage node)
		{
			if (node == null)
				throw new ArgumentNullException("node");

			var newTrades = node.Events;
			var firstTrade = newTrades.First();

			var si = GetSecurityAuction(firstTrade.Security);

			if (si.EmulateByOrderLog)
				ProcessDataError(new InvalidOperationException("В режиме ордерлога входные сделки запрещены."));

			si.LastTrade = newTrades.LastOrDefault();

			if (si.Depth != null && Settings.SyncDepthToTrades)
			{
				if (firstTrade.Time < si.Depth.LastChangeTime)
					throw new InvalidOperationException("Сделки устарели по сравнению со стаканом от {0:HH:mm:ss.fff}.".Put(si.Depth.LastChangeTime));

				si.Trades.AddRange(newTrades);
				
				si.GetDepth = di =>
				{
					di.Depth = DepthClone(di.Depth);
					di.Depth.EmulateTrades(di.Trades);
					di.GetDepth = d => d.Depth;
					return di.Depth;
				};
			}

			try
			{
				foreach (var sec in si.CachedOrders)
				{
					var depth = si.GetDepth(si);
					EvaluateByTrades(sec, newTrades, depth);
				}
			}
			catch (Exception e)
			{
				ProcessDataError(e);
			}
				
			BufferOutputMessage<Trade>(si.Security, MarketDataTypes.Trades, () => new TradeMessage(CurrentTime, newTrades), newTrades);
		}

		/// <summary>
		/// Запущен ли эмулятор.
		/// </summary>
		public virtual bool IsStarted { get; private set; }

		/// <summary>
		/// Сброосить состояние эмулятора на первоначальное.
		/// </summary>
		public virtual void Reset(DateTime time)
		{
			_incomingQueue.Clear();
			
			_byId.Clear();
			_bySecurity.Clear();

			_lastMessageTime.Clear();
			_outputBuffer.Clear();
			
			_orders.Clear();
			_orderRegisterFails.Clear();
			_orderCancelFails.Clear();
			_myTrades.Clear();
			_firstOrderTime = _lastOrderTime = default(DateTime);
			_lastFlushTime = default(DateTime);
			_flushBuffers = false;
			_currentTime = time;
		}

		/// <summary>
		/// Запустить эмулятор.
		/// </summary>
		public virtual void Start()
		{
			//_incomingQueue.Clear();	// PYH: сейчас смысл Emulator.Start/Stop неясен.
			//Reset();
			IsStarted = true;
		}

		/// <summary>
		/// Остановить эмулятор.
		/// </summary>
		public virtual void Stop()
		{
			IsStarted = false;
		}

		private void OnCurrentTimeChanged()
		{
			FlushMessageBuffer();
		}

		private void RaiseOutgoingMessages(DateTime nextTime)
		{
			Message msg;
			while ((msg = _outgoingQueue.TryPeek()) != null && msg.Time <= nextTime)
			{
				msg = _outgoingQueue.Dequeue();
				CurrentTime = msg.Time;
				RaiseMessage(msg);
			}
		}

		private void RaiseMessage(Message msg)
		{
			AddDebugLog("CL", () => msg);
			msg.RaiseEvents(this);
			
			MessageReceived.SafeInvoke(msg);	// можем здесь поставить точку останова и увидеть событие на графиках.
		}

		/// <summary>
		/// Добавить сообщение в очередь и провести его эмуляцию (и более ранних сообщений).
		/// </summary>
		/// <param name="msg">Входное сообщение.</param>
		public void Emulate(Message msg)
		{
			if (msg == null)
				throw new ArgumentNullException("msg");

			EnqueueMessage(msg);

			Message next;
			do
			{
				//if (!CanActivate(msg))
				//	return;
				next = _incomingQueue.Peek();

				while (true)
				{
					// есть ли в исходящей очереди сообщения предшествующие следующему входящему?
					var nextOut = _outgoingQueue.TryPeek();

					if (nextOut == null || nextOut.Time > next.Time)
						break; // исходящие все в будущем, входящее найдено

					// продолжаем смотреть исходящие
					nextOut = _outgoingQueue.Dequeue();
					CurrentTime = nextOut.Time;
					RaiseMessage(nextOut);
					next = _incomingQueue.Peek();
				}

				next = _incomingQueue.Dequeue();

				CurrentTime = next.Time;

				try
				{
					var next1 = next;
					AddDebugLog("EM", () => next1);
					next.Emulate(this);
				}
				catch (Exception ex)
				{
					ProcessDataError.SafeInvoke(ex);
				}

				FlushMessageBuffer();
			}
			while (next != msg);

			RaiseOutgoingMessages(CurrentTime);
		}

		private OrderLogItem _currentItem;
		private readonly List<Trade> _currentTrades = new List<Trade>();

		private void FlushOrder(OrderLogItem cancel, OrderLogItem register)
		{
			if (null != _currentItem)
			{
				if (Settings.UseOrderLogTrades)
				{
					_matches.SafeAdd(_currentItem.Order.Id).AddRange(_currentTrades);
				}

				if (null != cancel && cancel.Order.Id == _currentItem.Order.Id)
				{
					_currentItem.Order.Volume -= cancel.Order.Volume;
					cancel = null;
				}

				if (_currentItem.Order.Volume > 0)
					PutOrder(_currentItem.Order,OrderTraceActions.Registered);
			}

			_currentItem = register;

			if (null != cancel)
				Cancel(cancel.Order);
		}

		private void AddTrade(OrderLogItem item)
		{
			if (!Settings.UseOrderLogTrades)
				return;

			if (item.Order.Id == _currentItem.Order.Id)
				_currentTrades.Add(item.Trade);
			else
			{
				if (!_matches.ContainsKey(item.Order.Id))
					ProcessDataError(new InvalidOperationException("Ордерлог: Заявка {0} не найдена для сделки {1}.".Put(item.Order.Id, item.Trade.Id)));

				_matches.SafeAdd(item.Order.Id).Add(item.Trade);
			}
		}

		internal void OnOrderLog(OrderLogMessage tr)
		{
			var si = GetSecurityAuction(tr.Events[0].Order.Security);

			if (si.EmulateByOrderLog)
			{
				foreach (var item in tr.Events)
				{
					//Debug.WriteLine(item.ToString());
					if (!item.Order.IsSystem)
						continue;

					if (item.IsRegistered())
						FlushOrder(null, item);
					else if (item.IsCanceled())
						FlushOrder(item, null);
					else if (item.IsMatched())
						AddTrade(item);
					else
						throw new InvalidOperationException("Неизвестная запись лога заявок {0}.".Put(item));
				}
				
				FlushOrder(null, null);
			}
			else
			{
				// в режиме по сделкам-стаканам отправляем ордерлог дальше в стратегию.
				BufferOutputMessage<OrderLogItem>(si.Security, MarketDataTypes.OrderLog, () => tr, tr.Events);
			}
		}

		internal void OnClearing(ClearingMessage tr)
		{
			FlushOrder(null,null);

			foreach (var e in tr.Events)
			{
				Clearing(e.Security);
			}
		}

		internal void OnOrderTransaction(OrderTransactionMessage tr)
		{
			switch (tr.Action)
			{
				case OrderTraceActions.Canceling:
					OnCancel(tr.Order);
					break;
				case OrderTraceActions.Registering:
					OnRegister(tr.Order);
					break;
				case OrderTraceActions.ReRegistering:
					OnReRegister(tr.Order,tr.NewOrder);
					break;
				default:
					throw new ArgumentOutOfRangeException("tr", tr.Action, "Неизвестная операция.");
			}
		}

		internal void RaiseOrderEvents(OrderStateMessage msg)
		{
			foreach (var e in msg.Events)
			{
				switch (e.Action)
				{
					case OrderTraceActions.Registered:
						RaiseNewOrder(e.Order);
						break;
					case OrderTraceActions.Changed:
						RaiseOrderChanged(e.Order);
						break;
					case OrderTraceActions.Failed:
						RaiseOrderFailed(e.Fail);
						break;
					default:
						throw new ArgumentOutOfRangeException("msg", e.Action, "Неизвестная операция.");
				}
			}
		}

		private DateTime _currentTime;

		/// <summary>
		/// Текущее время в эмуляторе.
		/// </summary>
		public DateTime CurrentTime
		{
			get { return _currentTime; }
			set
			{
				// TODO: валимся в RTEmu.

				// if (value < _currentTime)
				//	throw new ArgumentOutOfRangeException("value", value, "Новое время меньше текущего {0}.".Put(_currentTime));

				_currentTime = value;
				OnCurrentTimeChanged();
			}
		}

		private TimeZoneInfo _emulatorTimeZone = TimeZoneInfo.Local;

		/// <summary>
		/// Временная зона, в которой работает эмулятор. По-умолчанию значение равно <see cref="TimeZoneInfo.Local"/>.
		/// </summary>
		public TimeZoneInfo EmulatorTimeZone
		{
			get { return _emulatorTimeZone; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_emulatorTimeZone = value;
			}
		}

		private void OnCancel(Order order)
		{
			Cancel(order);
		}

		private void ProcessError(Order order, OrderTraceActions sourceAction, Exception e)
		{
			var clone = Auction.CloneOrderState(order);

			if (sourceAction != OrderTraceActions.Canceling)
				clone.Id = 0;

			if (clone.Time == default(DateTime))
			{
				clone.Time = clone.LastChangeTime = EmulatorToExchange(order.Security, CurrentTime);
			}

			AddDebugLog("ERR {0} {1}\n  {2}", order, sourceAction, e.Message);

			var fail = new OrderFail
			{
				Order = clone,
				Error = e
			};
			var msg = new OrderStateMessage(CurrentTime, OrderTraceActions.Failed, fail);

			// ошибка это reply transaction в плаза, надо проверить подвергается ли она 75мс буферизации
			EnqueueOutputMessage(msg);
		}

		private DateTime EmulatorToExchange(Security security, DateTime time)
		{
			return security.ExchangeBoard.Exchange.ToExchangeTime(time, EmulatorTimeZone);
		}

		internal void OnTimeChanged(TimeMessage msg)
		{
			EnqueueOutputMessage(msg);
		}

		private void OnRegister(Order order)
		{
			PutOrder(order, OrderTraceActions.Registered);
		}

		private void PutOrder(Order order, OrderTraceActions action)
		{
			try
			{
				var si = GetSecurityAuction(order.Security);

				var state = si.New(order);
				
				if (si.EmulateByOrderLog)
					si.Put(state);
				else
				{
					if (state.State != OrderStates.Active)
						throw new InvalidOperationException("State!=Active");

					EvaluateNew(state, action);
				}
			}
			catch (Exception e)
			{
				ProcessDataError(e);
			}
		}

		private void Cancel(Order order)
		{
			try
			{
				var node = GetNode(order.Id);

				if (null == node)
				{
					if (IsStrategyOrder(order))
						ProcessError(order, OrderTraceActions.Canceling, 
							new InvalidOperationException("Заявка {0} не зарегистрирована, исполнена или уже отклонена.".Put(order.TransactionId)));
					else
						ProcessDataError(new InvalidOperationException("Ордерлог: Заявка {0} не зарегистрирована, исполнена или уже отклонена.".Put(order.Id)));

					return;
				}

				GetSecurityAuction(order.Security).Cancel(node);
			}
			catch (Exception e)
			{
				ProcessDataError(e);
			}
		}

		private void Clearing(Security security)
		{
			try
			{
				var si = GetSecurityAuction(security);
				
				si.LastTrade = null;
				si.Depth = null;
				si.Trades.Clear();

				foreach (var order in si.CachedOrders)
				{
					Cancel(order);

					if (IsStrategyOrder(order))
						PutOrder(order,OrderTraceActions.Registering);
				}
			}
			catch (Exception e)
			{
				ProcessDataError(e);
			}
		}

		private void OnReRegister(Order order, Order newOrder)
		{
			try
			{
				var node = GetNode(order.Id);

				if (node == null)
				{
					// отсылаем OrderFail с новой транзакцией.
					ProcessError(newOrder, OrderTraceActions.ReRegistering,
								new InvalidOperationException("Перерегистрации заявки {0} невозможна. Заявка не найдена.".Put(order.Id)));
					return;
				}

				if (node.Value.State != OrderStates.Active)
					throw new InvalidOperationException("Перерегистрируемая заявка неактивна");

				if (newOrder.Id != 0)
					throw new ArgumentException("Заявка {0} уже была ранее зарегистрирована.".Put(newOrder.TransactionId), "newOrder");

				if (newOrder.Security != order.Security)
					throw new ArgumentException("Заявки {0} и {1} имеют разные инструменты {2} и {3}."
										.Put(order.TransactionId, newOrder.TransactionId, order.Security, newOrder.Security));

				if (newOrder.Volume == 0)
					newOrder.Volume = node.Value.Volume;

				var si = GetSecurityAuction(order.Security);
				si.Cancel(node);

				PutOrder(newOrder, OrderTraceActions.ReRegistering);
			}
			catch (Exception e)
			{
				ProcessDataError(e);
			}
		}

		private bool EvaluateConditional(MarketDepth depth, Order order)
		{
			if (order.Type == OrderTypes.Conditional)
			{
				if (depth != null)
				{
					var newDerivedOrders = order.Condition.TryActivate(depth);

					if (newDerivedOrders != null && !newDerivedOrders.IsEmpty())
					{
						order.State = OrderStates.Done;
						order.LastChangeTime = CurrentTime;
						AuctionOnOrderChanged(order);

						foreach (var newDerivedOrder in newDerivedOrders)
							RegisterOrder(newDerivedOrder);
					}

					return true;
				}
			}

			return false;
		}

		private void EvaluateNew(Order order, OrderTraceActions action)
		{
			var si = GetSecurityAuction(order.Security);
			
			var newNode = si.PutInQueue(order);	// запоминаем тк надо сделки потом сводить по этим заявкам

			try
			{
				var depth = si.GetDepth(si);

				if (EvaluateConditional(depth, order))
					return/* true*/;

				if (EvaluateByDepth(order, depth, true))
					return/* true*/;

				if (depth == null)
				{
					// без стакана маркетные(они всегда новые) и новые лимитные-в-рынок матчатся по посл. сделке
					var last = si.LastTrade;

					if (last != null && last.Price != 0)
					{
						EvaluateByLast(order, last);
						//if (EvaluateByLast(order, last))
						//	return true;
					}
					else
					{	
						// если нет никаких цен, то фэйл
						ProcessError(order,action, new InvalidOperationException("Невозможно зарегистрировать заявку {0}: нет ни стаканов ни последней цены.".Put(order.TransactionId)));
						si.Remove(newNode);
					}
				}
			}
			catch (Exception e)
			{
				ProcessDataError(e);
			}

			//return false;
		}

		private decimal GetSlippagePrice(Order order, decimal lastPrice)
		{
			if (order.Direction == OrderDirections.Buy)
			{
				return  (decimal)(lastPrice + GetSettings(order.Security).Slippage);
			}
			else
			{
				return (decimal)(lastPrice - GetSettings(order.Security).Slippage);
			}
		}

		private void EvaluateByLast(Order order, Trade lastTrade)
		{
			if (lastTrade == null)
				throw new ArgumentNullException("lastTrade");

			if (lastTrade.Price == 0)
				throw new ArgumentException("Сделка {0} имеет нулевую цену.".Put(lastTrade.Id), "lastTrade");

			// Если тестируем без стакана, то маркетные ордера 
			// и новые-лимитные-в-рынок филим с фикс. уровнем проскальзывания

			var doMatch = order.Type == OrderTypes.Market; // маркетные матчим всегда

			// считаем цену исполнения ордера 
			var price = GetSlippagePrice(order, lastTrade.Price);

			if (order.Type == OrderTypes.Limit)// лимитные с ценой меньше расчетной оставляем в стакане.
				doMatch = order.Direction == OrderDirections.Buy ? order.Price > price : order.Price < price;

			var crnode = GetSecurityAuction(order.Security).FindCrossRefs(order);

			if (CheckCrossRefs(crnode, order, price))
				return/* true*/;

			if (doMatch)
			{
				Fill(order, order.Volume, price);
				//if (Fill(order, order.Volume, price))
				//	return true;
			}

			//return false;
		}

		private bool CheckCrossRefs(LinkedListNode<Order> crnode, Order order, decimal price)
		{
			if (crnode != null)
			{
				var sign = order.Direction == OrderDirections.Buy ? 1 : -1;

				if (decimal.Compare(crnode.Value.Price, price) != sign)
				{
					Cancel(order);

					ProcessError(order, OrderTraceActions.Registering, 
						new InvalidOperationException("Кросс-сделка для заявок {0} и {1}.".Put(order.TransactionId, crnode.Value.TransactionId)));

					return true;
				}
			}

			return false;
		}

		private bool EvaluateByDepth(Order order, MarketDepth depth, bool isNewOrder)
		{
			if (depth == null)
				return false;

			if (order == null)
				throw new ArgumentNullException("order");

			if (depth.IsFullEmpty())
				throw new ArgumentOutOfRangeException("depth");

			var sign = order.Direction == OrderDirections.Buy ? 1 : -1;
			var quotes = order.Direction == OrderDirections.Buy ? depth.Asks : depth.Bids;

			var crnode = GetSecurityAuction(order.Security).FindCrossRefs(order);

			if (quotes.Length == 0 || decimal.Compare(quotes[0].Price, order.Price) != sign)
			{
				// новые ордера. заявка попала в противоположную (либо противоположных нет - тогда с проскальзыванием)
				var length = quotes.Length;
				for (var i = 0; i < length; i++)
				{
					var quote = quotes[i];
					var cmp = decimal.Compare(quote.Price, order.Price);

					if (order.Type != OrderTypes.Market && cmp == sign)
					{
						// наш ордер большой. Поставить в стакан мы его не можем (это требует изменения стакана).
						// поэтому или филим целиком (это оптимистично) или отменяем остаток.
						switch (Settings.FillMode)
						{
							case MarketEmulatorSettings.FillModes.Fill:
								Fill(order, order.Balance, order.Price);
								break;
							case MarketEmulatorSettings.FillModes.Cancel:
								Cancel(order);
								break;
						}

						return order.State == OrderStates.Done;
					}
					
					if (quote.Volume <= 0)
						continue;

					// цена берется из встречного ордера для новых ордеров или из нашего ордера для старых.
					var fillPrice = isNewOrder ? quote.Price : order.Price;
					
					if (CheckCrossRefs(crnode, order, fillPrice))
						return true;

					if (Fill(order, order.Balance.Min(quote.Volume), fillPrice))
						return true;
				}

				if (order.State != OrderStates.Done)
				{
					// если заявка съела стакан, исполняем ее с проскальзыванием относительно края стакана.
					var basePrice = depth.Bids.Length > 0 ? depth.Bids[0].Price : depth.Asks[0].Price;

					var fillPrice = length > 0
					                	? GetSlippagePrice(order, quotes[length - 1].Price)
					                	: GetSlippagePrice(order, basePrice);

					Fill(order, order.Balance, fillPrice);
				}

				return order.State == OrderStates.Done;
			}
			else
			{
				if (CheckCrossRefs(crnode, order, order.Price))
					return false;
			}

			return false;
		}

		private void EvaluateByTrades(Order order, IEnumerable<Trade> trades, MarketDepth depth)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (order.Type != OrderTypes.Limit)
				throw new ArgumentException("Заявка {0} имеет неправильный тип {1}.".Put(order.TransactionId, order.Type), "order");

			if (trades == null)
				throw new ArgumentNullException("trades");

			Quote quote = null;
			var quoteChecked = depth == null;

			foreach (var trade in trades.Where(t => t.IsSystem && (order.Direction == OrderDirections.Buy ? t.Price <= order.Price : t.Price >= order.Price)))
			{
				var tradeVolume = trade.Volume;

				if (order.Price == trade.Price)
				{
					if (Settings.FillOnTouch)
					{
						if (!quoteChecked)
						{
							// если в стакане есть квота с ценой сделки, 
							// то наш ордер не филлим тк он последний в очереди
							// при этом (см SyncDepthToTrades) у нас из стакана вырезаны
							// квоты пофилленые сделками к текущему моменту.
							quote = depth.GetQuote(order.Price);
							quoteChecked = true;
						}

						if (quote != null)
							continue;
						// если квоты нет, значит сделка ее пофилила
						// тут же стоит наша заява. Здесь эвристика, что нашу заявку на 
						// объем этой сделки тоже филят.
					}
					else
						continue; // в режиме FillOnTouch=false НЕ филим ордера сделками с той же ценой
				}
				else if (Settings.FillMode == MarketEmulatorSettings.FillModes.Fill)
					tradeVolume = order.Balance;

				if (tradeVolume <= 0)
					continue;

				// если от трейда остался объем, то он потенциально может филить
				if (Fill(order, tradeVolume.Min(order.Balance), order.Price))
					return/* true*/;
			}

			//return false;
		}

		// возвращает true если ордер полностью зафиллен
		private bool Fill(Order state, decimal volume, decimal fillPrice)
		{
			if (null == state || null == state || state.Id == 0 || state.State != OrderStates.Active)
				throw new InvalidOperationException("Неверное состояние " + state);

			if (volume <= 0 || volume > state.Balance)
				throw new ArgumentOutOfRangeException("volume", volume, "Объем cделки неправильный.");

			// ордер всегда сначала помещен в node
			var node = GetNode(state.Id); 
			if (node == null)
				throw new InvalidOperationException("no node for " + state.Id);
			
			GetSecurityAuction(state.Security).Fill(node, null, fillPrice, volume);

			return state.State == OrderStates.Done;
		}

		private static bool IsStrategyOrder(Order order)
		{
			return order.Portfolio != null;
		}

		private void AuctionOnNewOrder(Order order)
		{
			var si = GetSecurityAuction(order.Security);

			if (si.EmulateByOrderLog)
			{
				// запись об ордерлоге
				BufferOrderLog(order.Security, OrderTraceActions.Registered, order, null);
			}

			if (!IsStrategyOrder(order))
				return;

			var item = new OrderTraceItem
			{
				Time = CurrentTime,
				Action = OrderTraceActions.Registered,
				Order = Auction.CloneOrderState(order)
			};
			// reply-transaction не подвергается 75мс буферизации
			//var msg = new OrderStateMessage(CurrentTime, OrderTraceActions.Registered, Auction.CloneOrderState(order));
			var msg = new OrderStateMessage(CurrentTime, item);
			EnqueueOutputMessage(msg);
		}

		private void AuctionOnOrderChanged(Order order)
		{
			var si = GetSecurityAuction(order.Security);

			if (si.EmulateByOrderLog)
			{
				// запись об ордерлоге
				BufferOrderLog(order.Security, OrderTraceActions.Changed, order, null);
			}

			if (!IsStrategyOrder(order))
				return;

			var item = new OrderTraceItem
			{
				Time = CurrentTime,
				Action = OrderTraceActions.Changed,
				Order = Auction.CloneOrderState(order)
			};

			BufferOutputMessage(order.Security, MarketDataTypes.OrderState, () => new OrderStateMessage(CurrentTime, item), item);
		}

		private void AuctionOnNewTrade(Order queuedState, Order newState, Trade trade)
		{
			if (queuedState == null)
				throw new ArgumentNullException("queuedState");

			var si = GetSecurityAuction(queuedState.Security);

			AddDebugLog("FILL {0} | {1} | {2}", trade, queuedState, newState);
			if (si.EmulateByOrderLog)
			{
				// ордерлог-запись о сделке
				BufferOrderLog(si.Security, OrderTraceActions.NewMyTrade, Auction.CloneOrderState(newState ?? queuedState), trade);

				// просто сделка сгенерированная по ордерлогу.
				BufferOutputMessage(si.Security, MarketDataTypes.Trades, () => new TradeMessage(CurrentTime, trade), trade);
			}

			if (IsStrategyOrder(queuedState))
			{
				var myTrade = new MyTrade
				{
					Trade = trade,
					Order = Auction.CloneOrderState(queuedState)
				};

				BufferOutputMessage(si.Security, MarketDataTypes.MyTrades, () => new MyTradeMessage(CurrentTime, myTrade), myTrade);
			}
			
			if (newState!=null && IsStrategyOrder(newState))
			{
				var myTrade = new MyTrade
				{
					Trade = trade,
					Order = Auction.CloneOrderState(newState)
				};

				BufferOutputMessage(si.Security, MarketDataTypes.MyTrades, () => new MyTradeMessage(CurrentTime, myTrade), myTrade);
			}
		}

		private void BufferOutputMessage<TData>(Security security, MarketDataTypes marketType, Func<Message> createFunc, TData next)
		{
			BufferOutputMessage<TData>(security, marketType, createFunc, new[] { next });
		}

		private void BufferOutputMessage<TData>(Security security, MarketDataTypes marketType, Func<Message> createFunc, IEnumerable<TData> next)
		{
			bool isNew;
			var buffer = GetMessageBuffer(security, marketType, createFunc, out isNew);

			if (isNew)
				return;

			var msg = buffer.Message as Message<TData>;

			if (msg == null)
				throw new InvalidOperationException();

			msg.Events.AddRange(next);
		}

		private void BufferOrderLog(Security security, OrderTraceActions action, Order state, Trade trade)
		{
			var item = new OrderTraceItem
			{
				Time = CurrentTime,
				Action = action,
				Order = Auction.CloneOrderState(state),
				Trade = trade
			}; 

			item.Order.Time = item.Order.LastChangeTime = CurrentTime;

			BufferOutputMessage<OrderLogItem>(security, MarketDataTypes.OrderLog, 
					() => new OrderLogMessage(CurrentTime,item), item);
		}

		private sealed class MessageBuffer
		{
			private readonly Func<Message> _createMessage;

			public MessageBuffer(Func<Message> createMessage)
			{
				_createMessage = createMessage;
			}

			private Message _message;

			public Message Message
			{
				get { return _message ?? (_message = _createMessage()); }
			}
		}

		private readonly SynchronizedDictionary<Tuple<Security, MarketDataTypes>, MessageBuffer> _outputBuffer =
			new SynchronizedDictionary<Tuple<Security, MarketDataTypes>, MessageBuffer>();

		private MessageBuffer GetMessageBuffer(Security s, MarketDataTypes messageType, Func<Message> createMessage, out bool isNew)
		{
			return _outputBuffer.SafeAdd(new Tuple<Security, MarketDataTypes>(s, messageType),  pair => new MessageBuffer(createMessage), out isNew);
		}

		internal void BufferOutputMessage(Message message)
		{
			bool isNew;

			var buffer = GetMessageBuffer(message.Security, message.DataType, () => message, out isNew);

			
			if (!isNew)
				buffer.Message.Append(message);
		}

		private void AuctionDepthChanged(Auction src)
		{
			bool isNew;
			GetMessageBuffer(src.Security,
			                  MarketDataTypes.MarketDepth, 
			                   () => new DepthMessage(CurrentTime, src.GetDepth(src)), out isNew);
		}

		private bool _flushBuffers;
		private DateTime _lastFlushTime;

		private void FlushMessageBuffer()
		{
			if (!_flushBuffers && CurrentTime - _lastFlushTime < Settings.MaxFlushTime)
				return;

			_flushBuffers = false;
			_lastFlushTime = CurrentTime;

			var buffers = _outputBuffer.SyncGet(c =>
			{
				var result = c.Values.ToArray();
				c.Clear();
				return result;
			});

			foreach (var buffer in buffers)
			{
				EnqueueOutputMessage(buffer.Message);
			}

			// ставим таймер
			if (Settings.MaxFlushTime != TimeSpan.Zero)
				EnqueueMessage(new TimeMessage(CurrentTime + Settings.MaxFlushTime, CurrentTime + Settings.MaxFlushTime));
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			Stop();
			base.DisposeManaged();
		}
	}
}
